﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DownloadFile.cs" company="3po.ru">
//   Author: Alexander Ulitin [support@3po.ru]
//   
//   This file is part of Conusic.
//   
//   Conusic is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//   
//   Conusic is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//   
//   You should have received a copy of the GNU General Public License
//   along with Conusic.  If not, see http://www.gnu.org/licenses/.
// </copyright>
// <summary>
//   The download file.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Conusic
{
    using System;
    using System.IO;
    using System.Net;
    using System.Threading;

    /// <summary>
    /// The download file.
    /// </summary>
    public sealed class DownloadFile
    {
        #region Constants

        /// <summary>
        /// The buffer size.
        /// </summary>
        private const int BufferSize = 1024 * 16;

        #endregion

        #region Fields

        /// <summary>
        /// The buffer.
        /// </summary>
        public byte[] Buffer;

        /// <summary>
        /// The readed bytes.
        /// </summary>
        public int ReadedBytes;

        /// <summary>
        /// The file event args.
        /// </summary>
        private readonly FileEventArgs fileEventArgs;

        /// <summary>
        /// The save to file.
        /// </summary>
        private readonly string saveToFile;

        /// <summary>
        /// The url.
        /// </summary>
        private readonly string url;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DownloadFile"/> class.
        /// </summary>
        /// <param name="url">
        /// The url.
        /// </param>
        /// <param name="saveToFile">
        /// The save to file.
        /// </param>
        public DownloadFile(string url, string saveToFile)
        {
            this.OnCompleteResetEvent = new ManualResetEvent(false);

            this.fileEventArgs = new FileEventArgs(this);
            this.url = url;
            this.saveToFile = saveToFile;
        }

        #endregion

        #region Delegates

        /// <summary>
        /// The file event handler.
        /// </summary>
        /// <param name="args">
        /// The args.
        /// </param>
        public delegate void FileEventHandler(FileEventArgs args);

        #endregion

        #region Public Events

        /// <summary>
        /// The on file complete.
        /// </summary>
        public event FileEventHandler OnFileComplete;

        /// <summary>
        /// The on file fail.
        /// </summary>
        public event FileEventHandler OnFileFail;

        /// <summary>
        /// The on progress file.
        /// </summary>
        public event FileEventHandler OnProgressFile;

        /// <summary>
        /// Gets or sets the on complete reset event.
        /// </summary>
        public ManualResetEvent OnCompleteResetEvent { get; set; }

        /// <summary>
        /// The save to file.
        /// </summary>
        public string SaveToFile
        {
            get
            {
                return this.saveToFile;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The download.
        /// </summary>
        /// <exception cref="WebException">
        /// </exception>
        public void Download()
        {
            // Fixed download count execution. Stop, until semaphore is busy.
            Config.Instance.DownloadPool.WaitOne();

            this.fileEventArgs.Url = this.url;
            this.fileEventArgs.SaveToFile = this.SaveToFile;

            // Declare object for safe download and release semaphore
            FileStream outputTempFile = null;
            FileStream outputLockFile = null;
            HttpWebRequest request = null;
            Stream datastream = null;

            // We want to save only complete file
            // so save download part in temp file
            string tempFileName = Path.GetTempFileName();
            try
            {
                // Lock and create file for download
                outputLockFile = new FileStream(this.SaveToFile, FileMode.CreateNew);

                outputTempFile = new FileStream(tempFileName, FileMode.Create);
                request = (HttpWebRequest)WebRequest.Create(this.url);

                // Set proxy configuration for request
                Config.Instance.SetProxyConfiguration(request);

                WebResponse response = request.GetResponse();
                datastream = response.GetResponseStream();

                if (datastream == null)
                {
                    throw new WebException("Response stream is null");
                }

                this.ReadedBytes = 0;
                this.fileEventArgs.Total = response.ContentLength;

                long totalReaded = 0;
                this.Buffer = new byte[BufferSize];
                do
                {
                    this.ReadedBytes = datastream.Read(this.Buffer, 0, BufferSize);

                    outputTempFile.Write(this.Buffer, 0, this.ReadedBytes);
                    totalReaded += this.ReadedBytes;

                    this.fileEventArgs.Downloaded = totalReaded;
                    if (this.OnProgressFile != null)
                    {
                        this.OnProgressFile(this.fileEventArgs);
                    }
                }
                while (totalReaded < response.ContentLength);
                outputTempFile.Close();

                // Unlock file and copy
                outputLockFile.Close();
                File.Copy(tempFileName, this.SaveToFile, true);

                this.OnCompleteResetEvent.Set();
                if (this.OnFileComplete != null)
                {
                    this.OnFileComplete(this.fileEventArgs);
                }
            }
            catch (Exception e)
            {
                this.fileEventArgs.Exc = e;
                this.SafeDelete(this.SaveToFile);

                if (this.OnFileFail != null)
                {
                    this.OnFileFail(this.fileEventArgs);
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                this.SafeDelete(tempFileName);
                if (outputTempFile != null)
                {
                    outputTempFile.Close();
                }

                if (datastream != null)
                {
                    datastream.Close();
                }

                if (outputLockFile != null)
                {
                    outputLockFile.Close();
                }
            }

            Config.Instance.DownloadPool.Release();
        }

        /// <summary>
        /// The download async.
        /// </summary>
        public void DownloadAsync()
        {
            ThreadStart threadStart = this.Download;
            var thread = new Thread(threadStart);
            thread.Start();
        }

        #endregion

        #region Methods

        /// <summary>
        /// The safe delete.
        /// </summary>
        /// <param name="filename">
        /// The filename.
        /// </param>
        private void SafeDelete(string filename)
        {
            try
            {
                File.Delete(filename);
            }
            catch
            {
            }
        }

        #endregion
    }
}